package me.webase.dao;

import java.util.Collection;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.type.Type;

public class QueryBuilder {
	
	public Criteria criteria;
	
	public QueryBuilder(Criteria criteria){
		this.criteria = criteria;
	}
	
	/**
	 * Apply an "equal" constraint to the identifier property
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder idEq(Object value) {
		criteria.add(Restrictions.idEq(value));
		return this;
	}
	/**
	 * Apply an "equal" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder eq(String propertyName, Object value) {
		criteria.add(Restrictions.eq(propertyName, value));
		return this;
	}
	/**
	 * Apply a "not equal" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder ne(String propertyName, Object value) {
		criteria.add(Restrictions.ne(propertyName, value));
		return this;
	}
	/**
	 * Apply a "like" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder like(String propertyName, Object value) {
		criteria.add(Restrictions.like(propertyName, value));
		return this;
	}
	/**
	 * Apply a "like" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder like(String propertyName, String value, MatchMode matchMode) {
		criteria.add(Restrictions.like(propertyName, value, matchMode));
		return this;
	}

	/**
	 * A case-insensitive "like", similar to Postgres <tt>ilike</tt>
	 * operator
	 *
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder ilike(String propertyName, String value, MatchMode matchMode) {
		criteria.add(Restrictions.ilike(propertyName, value, matchMode));
		return this;
	}
	/**
	 * A case-insensitive "like", similar to Postgres <tt>ilike</tt>
	 * operator
	 *
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder ilike(String propertyName, Object value) {
		return ilike( propertyName, value.toString(), MatchMode.EXACT );
	}

	/**
	 * Apply a "greater than" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder gt(String propertyName, Object value) {
		criteria.add(Restrictions.gt(propertyName, value));
		return this;
	}
	/**
	 * Apply a "less than" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder lt(String propertyName, Object value) {
		criteria.add(Restrictions.lt(propertyName, value));
		return this;
	}
	/**
	 * Apply a "less than or equal" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder le(String propertyName, Object value) {
		criteria.add(Restrictions.le(propertyName, value));
		return this;
	}
	/**
	 * Apply a "greater than or equal" constraint to the named property
	 * @param propertyName
	 * @param value
	 * @return Criterion
	 */
	public QueryBuilder ge(String propertyName, Object value) {
		criteria.add(Restrictions.ge(propertyName, value));
		return this;
	}
	/**
	 * Apply a "between" constraint to the named property
	 * @param propertyName
	 * @param lo value
	 * @param hi value
	 * @return Criterion
	 */
	public QueryBuilder between(String propertyName, Object lo, Object hi) {
		criteria.add(Restrictions.between(propertyName, lo, hi));
		return this;
	}
	/**
	 * Apply an "in" constraint to the named property
	 * @param propertyName
	 * @param values
	 * @return Criterion
	 */
	public QueryBuilder in(String propertyName, Object[] values) {
		criteria.add(Restrictions.in(propertyName, values));
		return this;
	}
	/**
	 * Apply an "in" constraint to the named property
	 * @param propertyName
	 * @param values
	 * @return Criterion
	 */
	public QueryBuilder in(String propertyName, Collection values) {
		criteria.add(Restrictions.in(propertyName, values));
		return this;
	}
	/**
	 * Apply an "is null" constraint to the named property
	 * @return Criterion
	 */
	public QueryBuilder isNull(String propertyName) {
		criteria.add(Restrictions.isNull(propertyName));
		return this;
	}
	/**
	 * Apply an "equal" constraint to two properties
	 */
	public QueryBuilder eqProperty(String propertyName, String otherPropertyName) {
		criteria.add(Restrictions.eqProperty(propertyName, otherPropertyName));
		return this;
	}
	/**
	 * Apply a "not equal" constraint to two properties
	 */
	public QueryBuilder neProperty(String propertyName, String otherPropertyName) {
		criteria.add(Restrictions.neProperty(propertyName, otherPropertyName));
		return this;
	}
	/**
	 * Apply a "less than" constraint to two properties
	 */
	public QueryBuilder ltProperty(String propertyName, String otherPropertyName) {
		criteria.add(Restrictions.ltProperty(propertyName, otherPropertyName));
		return this;
	}
	/**
	 * Apply a "less than or equal" constraint to two properties
	 */
	public QueryBuilder leProperty(String propertyName, String otherPropertyName) {
		criteria.add(Restrictions.leProperty(propertyName, otherPropertyName));
		return this;
	}
	/**
	 * Apply a "greater than" constraint to two properties
	 */
	public QueryBuilder gtProperty(String propertyName, String otherPropertyName) {
		criteria.add(Restrictions.gtProperty(propertyName, otherPropertyName));
		return this;
	}
	/**
	 * Apply a "greater than or equal" constraint to two properties
	 */
	public QueryBuilder geProperty(String propertyName, String otherPropertyName) {
		criteria.add(Restrictions.geProperty(propertyName, otherPropertyName));
		return this;
	}
	/**
	 * Apply an "is not null" constraint to the named property
	 * @return Criterion
	 */
	public QueryBuilder isNotNull(String propertyName) {
		criteria.add(Restrictions.isNotNull(propertyName));
		return this;
	}
	/**
	 * Return the conjuction of two expressions
	 *
	 * @param lhs
	 * @param rhs
	 * @return Criterion
	 */
	public QueryBuilder and(Criterion lhs, Criterion rhs) {
		criteria.add(Restrictions.and(lhs, rhs));
		return this;
	}
	/**
	 * Return the conjuction of multiple expressions
	 *
	 * @param predicates The predicates making up the initial junction
	 *
	 * @return The conjunction
	 */
	public QueryBuilder and(Criterion... predicates) {
		criteria.add(Restrictions.and(predicates));
		return this;
	}
	/**
	 * Return the disjuction of two expressions
	 *
	 * @param lhs
	 * @param rhs
	 * @return Criterion
	 */
	public QueryBuilder or(Criterion lhs, Criterion rhs) {
		criteria.add(Restrictions.or(lhs, rhs));
		return this;
	}
	/**
	 * Return the disjuction of multiple expressions
	 *
	 * @param predicates The predicates making up the initial junction
	 *
	 * @return The conjunction
	 */
	public QueryBuilder or(Criterion... predicates) {
		criteria.add(Restrictions.or(predicates));
		return this;
	}
	/**
	 * Return the negation of an expression
	 *
	 * @param expression
	 * @return Criterion
	 */
	public QueryBuilder not(Criterion expression) {
		criteria.add(Restrictions.not(expression));
		return this;
	}
	/**
	 * Apply a constraint expressed in SQL, with the given JDBC
	 * parameters. Any occurrences of <tt>{alias}</tt> will be
	 * replaced by the table alias.
	 *
	 * @param sql
	 * @param values
	 * @param types
	 * @return Criterion
	 */
	public QueryBuilder sqlRestriction(String sql, Object[] values, Type[] types) {
		criteria.add(Restrictions.sqlRestriction(sql, values, types));
		return this;
	}
	/**
	 * Apply a constraint expressed in SQL, with the given JDBC
	 * parameter. Any occurrences of <tt>{alias}</tt> will be replaced
	 * by the table alias.
	 *
	 * @param sql
	 * @param value
	 * @param type
	 * @return Criterion
	 */
	public QueryBuilder sqlRestriction(String sql, Object value, Type type) {
		criteria.add(Restrictions.sqlRestriction(sql, value, type));
		return this;
	}
	/**
	 * Apply a constraint expressed in SQL. Any occurrences of <tt>{alias}</tt>
	 * will be replaced by the table alias.
	 *
	 * @param sql
	 * @return Criterion
	 */
	public QueryBuilder sqlRestriction(String sql) {
		criteria.add(Restrictions.sqlRestriction(sql));
		return this;
	}

	/**
	 * Group expressions together in a single conjunction (A and B and C...)
	 *
	 * @return Conjunction
	 */
	public QueryBuilder conjunction() {
		criteria.add(Restrictions.conjunction());
		return this;
	}

	/**
	 * Group expressions together in a single disjunction (A or B or C...)
	 *
	 * @return Conjunction
	 */
	public QueryBuilder disjunction() {
		criteria.add(Restrictions.disjunction());
		return this;
	}

	/**
	 * Apply an "equals" constraint to each property in the
	 * key set of a <tt>Map</tt>
	 *
	 * @param propertyNameValues a map from property names to values
	 * @return Criterion
	 */
	public QueryBuilder allEq(Map propertyNameValues) {
		criteria.add(Restrictions.allEq(propertyNameValues));
		return this;
	}

	/**
	 * Constrain a collection valued property to be empty
	 */
	public QueryBuilder isEmpty(String propertyName) {
		criteria.add(Restrictions.isEmpty(propertyName));
		return this;
	}

	/**
	 * Constrain a collection valued property to be non-empty
	 */
	public QueryBuilder isNotEmpty(String propertyName) {
		criteria.add(Restrictions.isNotEmpty(propertyName));
		return this;
	}

	/**
	 * Constrain a collection valued property by size
	 */
	public QueryBuilder sizeEq(String propertyName, int size) {
		criteria.add(Restrictions.sizeEq(propertyName, size));
		return this;
	}

	/**
	 * Constrain a collection valued property by size
	 */
	public QueryBuilder sizeNe(String propertyName, int size) {
		criteria.add(Restrictions.sizeNe(propertyName, size));
		return this;
	}

	/**
	 * Constrain a collection valued property by size
	 */
	public QueryBuilder sizeGt(String propertyName, int size) {
		criteria.add(Restrictions.sizeGt(propertyName, size));
		return this;
	}

	/**
	 * Constrain a collection valued property by size
	 */
	public QueryBuilder sizeLt(String propertyName, int size) {
		criteria.add(Restrictions.sizeLt(propertyName, size));
		return this;
	}

	/**
	 * Constrain a collection valued property by size
	 */
	public QueryBuilder sizeGe(String propertyName, int size) {
		criteria.add(Restrictions.sizeGe(propertyName, size));
		return this;
	}

	/**
	 * Constrain a collection valued property by size
	 */
	public QueryBuilder sizeLe(String propertyName, int size) {
		criteria.add(Restrictions.sizeLe(propertyName, size));
		return this;
	}

	/**
	 * Consider using any of the natural id based loading stuff from session instead, especially in cases
	 * where the restriction is the full set of natural id values.
	 *
	 * @see Session#byNaturalId(Class)
	 * @see Session#byNaturalId(String)
	 * @see Session#bySimpleNaturalId(Class)
	 * @see Session#bySimpleNaturalId(String)
	 */
	public QueryBuilder naturalId() {
		criteria.add(Restrictions.naturalId());
		return this;
	}
}
